package il.ac.technion.cs236700.address_book;

import il.ac.technion.cs236700.utils.Misc;
import il.ac.technion.cs236700.utils.CSV.Separator;
import il.ac.technion.cs236700.utils.DBC.DBC;
import il.ac.technion.cs236700.utils.composite.Component;
import il.ac.technion.cs236700.utils.composite.Composite;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

/** 
 * 
 * @authors Regev Azriel & Sharon Ronen
 *
 */
public class FlexibleAddressBookImpl extends AddressBookImpl implements FlexibleAddressBook {

	private List<Composite<Group>> root_groups = new ArrayList<Composite<Group>>();

	private static Comparator<Group> nameComparator = new Comparator<Group>(){
		@Override
		public int compare(Group o1, Group o2) {
			return o1.groupName().compareTo(o2.groupName());
		}			
	};

	public FlexibleAddressBookImpl(ArrayList<Contact> contacts) {
		super();
		super.setContacts(contacts);
	}

	@Override
	public void AddGroup(String groupName){
		int ls = groupName.lastIndexOf("/");
		Composite<Group> group = new Composite<Group>(new Group(groupName),null);
		if (ls == -1 && !root_groups.contains(group)){			
			root_groups.add(group);
			return;
		}
		String parentName = groupName.substring(0,ls);
		Composite<Group> parent = getGroup(parentName);
		DBC.require(parent != null, "parent  does not exist");
		if (parent.search(nameComparator, group.getData()) == null)
			parent.add(group); 						
	}

	@Override
	public void AddToGroup(String groupName, Person p) {
		DBC.require(p != null && groupName != null, "");
		Composite<Group> group = getGroup(groupName);
		if (group != null){
			Contact c = this.getContact(p);
			if (c == null) {
				this.add(p, null, null);
				c = this.getContact(p);
			}
			group.getData().contacts.add(c);
		}
	}


	@Override
	public void DeleteGroup(String groupName) {
		DBC.require(groupName != null, "");
		Composite<Group> group = getGroup(groupName);
		DBC.require(group != null, "group "+ groupName + "does not exist");
		Component<Group> parent = group.getParent();
		if (parent != null) parent.remove(group);
		else root_groups.remove(group);
	}

	@Override
	public void RemoveFromGroup(String groupName, Person p) {
		DBC.require(groupName != null, "");
		Composite<Group> group = getGroup(groupName);
		DBC.require(group != null, "group "+ groupName + "does not exist");
		Contact c = this.getContact(p);
		if (c == null) return;
		group.getData().contacts.remove(c);
	}

	@Override
	public List<String> getContainedGroups(Person p) {
		ArrayList<String> $ = new ArrayList<String>();
		for (Composite<Group> root : root_groups) {			
			$.addAll(recGetContainedGroups((Composite<Group>)root,p));
		}
		return $;
	}

	private ArrayList<String> recGetContainedGroups(Composite<Group> root, Person p) {
		ArrayList<String> $ = new ArrayList<String>();
		if (root.getData().contains(p)) $.add(root.getData().groupName());
		for (Component<Group> child : root.getChildren()) {
			$.addAll(recGetContainedGroups((Composite<Group>)child, p));
		}
		return $;
	}

	@Override
	public List<Contact> getGroupContacts(String groupName) {
		Composite<Group> group = getGroup(groupName);
		if (group != null){
			ArrayList<Contact> $ 
			= Misc.cast(new ArrayList<Contact>(),Misc.distinct(recGetGroupContacts(group).toArray()));
			Collections.sort($);
			return $;
		}
			
		return null;
	}

	private List<Contact> recGetGroupContacts(Component<Group> group) {
		List<Contact> $ = new ArrayList<Contact>();
		$.addAll(group.getData().contacts);
		for (Component<Group> child : group.getChildren()) {
			$.addAll(recGetGroupContacts(child));
		}
		return $;
	}

	private Composite<Group> getGroup(String groupName) {
		Composite<Group> group = null;
		for (Composite<Group> g : root_groups){
			if ((group = traverse(g, groupName))!=null){				
				return group;
			}
		}
		return null;
	}


	private Composite<Group> traverse(Composite<Group> root, String groupName){
		return (Composite<Group>) root.search(nameComparator, new Group(groupName));
	}


	@Override
	public void AddDetails(Person p, Map<String, String> details) {
		DBC.require(p != null, "");
		Contact con = find(p);
		DBC.require(con != null, "");	
		con.addToMap(details);
	}

	@Override
	public void AddSupportedKeys(ArrayList<String> keys) {
		DBC.require(keys != null, "");
		this.getSupportedKeysList().addAll(keys);
	}

	@Override
	public void Delete(Person p) {
		DBC.require(p != null, "");
		Contact con = find(p);
		DBC.require(con != null, "");
		if (!this.getContacts().remove(con)){
			System.out.println("Sorry!, can't delete this person");
		}
	}

	@Override
	public void DeleteDetails(Person p, String[] keys) {
		DBC.require(p != null, "");
		Contact con = find(p);
		DBC.require(con != null, "");
		for (String key : keys){
			con.getMap().remove(key);
		}
	}

	@Override
	public void UpdateDetails(Person p, Map<String, String> newDetails) {
		DBC.require(p != null, "");
		Contact con = find(p);
		DBC.require(con != null, "");
		con.setValue(newDetails);
	}

	@Override
	public String Search(String name) {
		String $ = new String();
		ArrayList<Contact> $$ = new ArrayList<Contact>();
		for(Contact con :  super.getContacts()) {
			if (con.FirstName().startsWith(name) || con.LastName().startsWith(name))
				$$.add(con);
		}
		Collections.sort($$);
		$ = new String(MakeCVSStrings($$));
		if ($ == "")
			return "Not found";
		return $;
	}


	@Override
	public ArrayList<String> SearchAccordingToKey(String key) {
		ArrayList<String> $ = new ArrayList<String>();
		for(Contact con : super.getContacts()){
			if(con.getMap().containsKey(key))
				$.add(con.getMap().get(key));
		}
		Collections.sort($);
		return $;
	}


	@Override
	public String SearchForPerson(Person p) {
		Contact con = super.find(p);
		String $ = MakeCVSString(con);
		if ($ == "")
			return "Not found";
		return $;
	}


	@Override
	public String Search(Map<String, String> map) {
		ArrayList<Contact> $$ = new ArrayList<Contact>();
		for(Contact con :  super.getContacts()) {
			if(con.containMap(map))
				$$.add(con);
		}
		Collections.sort($$);
		String $ = new String(MakeCVSStrings($$));
		if ($ == "")
			return "Not found";
		return $;
	}

	
	private String MakeCVSString(Contact con){
		if(con == null) return "";
		getOutputFieldsArray().clear();
		for(String key : con.getMap().keySet())
			getOutputFieldsArray().add(key);
		String $ = Separator.separateBy(getOutputFieldsArray(), ",")+"\n";
		$ += con.output((ArrayList<String>)getOutputFieldsArray())+"\n";
		return $;
	}

	private String MakeCVSStrings(ArrayList<Contact> contacats){
		//getOutputFieldsArray().clear();
		//getOutputFieldsArray().addAll(getSupportedKeysList());
		String $ = new String(Separator.separateBy(getOutputFieldsArray(), ",")+"\n");
		for (Contact c1 : contacats){
			for (Contact c2 : contacats) {
				if (c1.canUnify(c2)){					
					c1.unify(c2);
				}
			}
		}	
		for (Object c : Misc.distinct(contacats.toArray())) {
			DBC.require(c instanceof Contact, "");
			$+=((Contact)c).output((ArrayList<String>)getOutputFieldsArray())+"\n";
		}
		return $;
	}
	
	public List<Composite<Group>> getRootGroups() {
		return root_groups;
	}
}
